/*
 * Created on 04/11/2012 - 12:05 PM
 */
package sembr.vtk.mesh;

import java.util.ArrayList;
import vtk.*;

/**
 *
 * @author adriano
 */
public class vtkSolidGenerator extends MesherBase
{
    private vtkPolyData surface;
    private vtkUnstructuredGrid solidObject;
    private double[] bounds;
    private double resolution;
    private ArrayList<vtkPoints> intersectionCollection;
    private vtkIdListCollection cellListOfIntersections;
    private vtkOBBTree locator;
    private vtkPoints globalPoints;
    private vtkCleanPolyData polyDataCleaner;
    private vtkDecimatePro decimatePro;
        
    public vtkSolidGenerator(vtkPolyData surface, double resolution)
    {
       setSurface(surface);
       setBounds(surface.GetBounds());
       setResolution(resolution);
       setSolidObject(new vtkUnstructuredGrid());
       setGlobalPoints(new vtkPoints());       
       setCellListOfIntersections(new vtkIdListCollection());
       setIntersectionCollection(new ArrayList<vtkPoints>());
       setPolyDataCleaner(new vtkCleanPolyData());
       setDecimatePro(new vtkDecimatePro());
       setLocator(new vtkOBBTree());
       computeIntersections();
       solidifiedObjectByIntersections();
    }

    /**
     * @return the surface
     */
    public vtkPolyData getSurface() 
    {
        return surface;
    }

    /**
     * @param surface the surface to set
     */
    public final void setSurface(vtkPolyData surface) 
    {        
        this.surface = surface;
    }
    
    /**
     * @return the solidObject
     */
    @Override
    public vtkUnstructuredGrid getUnstructuredGrid() 
    {
        return solidObject;
    }

    /**
     * @param solidObject the solidObject to set
     */
    public final void setSolidObject(vtkUnstructuredGrid solidObject) 
    {
        this.solidObject = solidObject;
    }
    
    /**
     * @return the globalPoints
     */
    public vtkPoints getGlobalPoints() 
    {
        return globalPoints;
    }

    /**
     * @param globalPoints the globalPoints to set
     */
    public final void setGlobalPoints(vtkPoints globalPoints) 
    {
        this.globalPoints = globalPoints;
    }

    /**
     * @return the bounds
     */
    public double[] getBounds() 
    {
        return bounds;
    }

    /**
     * @param bounds the bounds to set
     */
    public final void setBounds(double[] bounds) 
    {
        this.bounds = bounds;
    }

    /**
     * @return the resolution
     */
    public double getResolution() 
    {
        return resolution;
    }

    /**
     * @param resolution the resolution to set
     */
    public final void setResolution(double resolution) 
    {
        this.resolution = resolution;
    }
    
    /**
     * @return the locator
     */
    public vtkOBBTree getLocator() 
    {
        return locator;
    }

    /**
     * @param locator the locator to set
     */
    public final void setLocator(vtkOBBTree locator) 
    {
        this.locator = locator;
        this.locator.SetDataSet(decimatePro.GetOutput());
        this.locator.BuildLocator();
    }
    
    /**
     * @return the cellListOfIntersections
     */
    public vtkIdListCollection getCellListOfIntersections() 
    {
        return cellListOfIntersections;
    }

    /**
     * @param cellListOfIntersections the cellListOfIntersections to set
     */
    public final void setCellListOfIntersections(vtkIdListCollection cellListOfIntersections) 
    {
        this.cellListOfIntersections = cellListOfIntersections;
    }
    
    /**
     * @return the intersectionCollection
     */
    public ArrayList<vtkPoints> getIntersectionCollection() 
    {
        return intersectionCollection;
    }

    /**
     * @param intersectionCollection the intersectionCollection to set
     */
    public final void setIntersectionCollection(ArrayList<vtkPoints> intersectionCollection) 
    {
        this.intersectionCollection = intersectionCollection;
    }
    
    /**
     * @return the polyDataCleaner
     */
    public vtkCleanPolyData getPolyDataCleaner() 
    {
        return polyDataCleaner;
    }

    /**
     * @param polyDataCleaner the polyDataCleaner to set
     */
    public final void setPolyDataCleaner(vtkCleanPolyData polyDataCleaner) 
    {
        this.polyDataCleaner = polyDataCleaner;
        this.polyDataCleaner.SetInput(surface);
    }
    
    /**
     * @return the decimatePro
     */
    public vtkDecimatePro getDecimatePro() 
    {
        return decimatePro;
    }

    /**
     * @param decimatePro the decimatePro to set
     */
    public final void setDecimatePro(vtkDecimatePro decimatePro) 
    {
        this.decimatePro = decimatePro;
        
      //  vtkTriangleFilter triangulator = new vtkTriangleFilter();
      //  triangulator.SetInput(getPolyDataCleaner().GetOutput());
       // triangulator.Update();        
        
        this.decimatePro.SetInputConnection(getPolyDataCleaner().GetOutputPort());
        this.decimatePro.SetTargetReduction(.10);
        this.decimatePro.Update();
    }
    
    private void computeIntersections()
    {
        // intersection lines are parallel to x's axes
        double xi = getBounds()[0];
        double xf = getBounds()[1];
        // lenghts for y and z dimensions
        double yLenght = getBounds()[3] - getBounds()[2];
        double zLenght = getBounds()[5] - getBounds()[4];
               
        double yCurrentCoord = getBounds()[2];
        double zCurrentCoord;       
       
        while (yCurrentCoord <= yLenght)
        {
            zCurrentCoord = getBounds()[4];
            
            while (zCurrentCoord <= zLenght)
            {
                computeCurrentIntersection(xi, xf, yCurrentCoord, zCurrentCoord);
                
                zCurrentCoord += 1 / getResolution();            
            }
            
            yCurrentCoord += 1 / getResolution();        
        }
    } 
    
    private void computeCurrentIntersection(double xi, double xf, double yCurrentCoord, double zCurrentCoord)
    {
        double[] initialPoint = {xi, yCurrentCoord, zCurrentCoord};
        double[] finalPoint = {xf, yCurrentCoord, zCurrentCoord};        
        vtkPoints intersections = new vtkPoints();        
        vtkIdList intersectionedCells = new vtkIdList();                
            
        locator.IntersectWithLine(initialPoint, finalPoint, intersections, intersectionedCells);        
        getIntersectionCollection().add(intersections);
        getCellListOfIntersections().AddItem(intersectionedCells);        
    }
    
    private void solidifiedObjectByIntersections()
    {         
        double[] initialPoint;
        double[] finalPoint;
        int globalVoxelId = 0;
                        
        for (int i = 0; i < getIntersectionCollection().size(); i++)
        {
            vtkPoints currentIntersections = getIntersectionCollection().get(i);
                        
            for (int j = 0; j < currentIntersections.GetNumberOfPoints(); j = j + 2)
            {
                // points representing the initial and final line vertices inside object 
                initialPoint = currentIntersections.GetPoint(j);
                finalPoint = currentIntersections.GetPoint(j + 1);
                
                globalVoxelId = generateVoxelsAlongLine(initialPoint, finalPoint, globalVoxelId);
            }           
        }
        
        getUnstructuredGrid().SetPoints(getGlobalPoints());
    }
    
    private int generateVoxelsAlongLine(double[] initialLinePoint, double[] finalLinePoint, int globalId)
    {
        double xCurrentLinePosition = initialLinePoint[0];
        double xFinalLinePosition = finalLinePoint[0];
        double shift = (1 / getResolution()) / 2;
        //double shift = 0.;
        double[] referencePoint = {initialLinePoint[0] - shift, initialLinePoint[1] - shift, initialLinePoint[2] - shift};
        double[][] verticesOfVoxel;
        vtkVoxel currentVoxel;
        
        while (xCurrentLinePosition <= xFinalLinePosition)
        {
            referencePoint[0] = xCurrentLinePosition - shift;                        
            
            verticesOfVoxel = generateVoxelPoints(referencePoint[0], referencePoint[1], referencePoint[2]);            
            currentVoxel = generateVoxel(verticesOfVoxel, globalId);
            
            // insert current voxel into solid object
            solidObject.InsertNextCell(currentVoxel.GetCellType(), currentVoxel.GetPointIds());            
            
            // it just need to increment x's axes
            // because voxels will be created along line in x coords direction
            xCurrentLinePosition += (1 / getResolution());
            
            globalId += 8;
        }
        
        return globalId;
    }
    
    private double[][] generateVoxelPoints(double xReference, double yReference, double zReference)
    {
        // set 8 points to match with 8 voxel´s points
        double[][] pointsCoords = new double[8][3];
        
        pointsCoords[0] = new double[]{xReference, yReference, zReference};
        pointsCoords[1] = new double[]{xReference + (1 / getResolution()), yReference, zReference};
        pointsCoords[2] = new double[]{xReference, yReference + (1 / getResolution()), zReference};
        pointsCoords[3] = new double[]{xReference + (1 / getResolution()), yReference + (1 / getResolution()), zReference};
        pointsCoords[4] = new double[]{xReference, yReference, zReference + (1 / getResolution())};
        pointsCoords[5] = new double[]{xReference + (1 / getResolution()), yReference, zReference + (1 / getResolution())};
        pointsCoords[6] = new double[]{xReference, yReference + (1 / getResolution()), zReference + (1 / getResolution())};
        pointsCoords[7] = new double[]{xReference + (1 / getResolution()), yReference + (1 / getResolution()), zReference + (1 / getResolution())};
        
        return pointsCoords;
    }
    
    private vtkVoxel generateVoxel(double[][] vertices, int globalId)
    {
        vtkVoxel currentVoxel = new vtkVoxel();
        int pointId;
        
        for (int i = 0; i < vertices.length; ++i)
        {
            pointId = globalId + i;
            // populate global points
            globalPoints.InsertPoint(pointId, vertices[i]);  
            currentVoxel.GetPointIds().SetId(i, pointId); 
        }
                
        return currentVoxel;
    }   
}
